// RHReliableDatagram.cpp
//
// Define addressed datagram
// 
// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers 
// (see http://www.hoperf.com)
// RHDatagram will be received only by the addressed node or all nodes within range if the 
// to address is RH_BROADCAST_ADDRESS
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// $Id: RHReliableDatagram.cpp,v 1.14 2015/08/13 02:45:47 mikem Exp mikem $

#include "RHReliableDatagram.h"

////////////////////////////////////////////////////////////////////
// Constructors
RHReliableDatagram::RHReliableDatagram(RHGenericDriver& driver, uint8_t thisAddress)
    : RHDatagram(driver, thisAddress)
{
    _retransmissions = 0;
    _lastSequenceNumber = 0;
    _timeout = RH_DEFAULT_TIMEOUT;
    _retries = RH_DEFAULT_RETRIES;
}

// Public methods
void RHReliableDatagram::setTimeout(int32_t timeout)
{
    _timeout = timeout;
}

void RHReliableDatagram::setRetries(uint8_t retries)
{
    _retries = retries;
}

uint8_t RHReliableDatagram::retries()
{
    return _retries;
}

bool RHReliableDatagram::sendtoWait(uint8_t* buf, uint8_t len, uint8_t address)
{
    uint8_t sequenceNumber = ++_lastSequenceNumber;
    uint8_t retries = 0;
	int32_t timeout;
	int32_t timeLeft;
	uint8_t from, to, id, flags;

    while (retries++ <= _retries)
    {
		setHeaderId(sequenceNumber);
		setHeaderFlags(RH_FLAGS_NONE, RH_FLAGS_ACK);	// Clear the ACK flag
		sendto(buf, len, address);

		if (! waitPacketSent(_timeout))
			return false;

		// Never wait for ACKS to broadcasts:
		if (address == RH_BROADCAST_ADDRESS)
			return true;

		if (retries > 1)
			_retransmissions++;

		uint32_t thisSendTime = millis();	// Timeout does not include original transmit time

		// Compute a new timeout, random between _timeout and _timeout * 2
		// This is to prevent collisions on every retransmit
		// if 2 nodes try to transmit at the same time
		timeout = _timeout + (_timeout * random(0, 256) / 256);
        while ((timeLeft = timeout - (int32_t)(millis() - thisSendTime)) > 0)
		{
			if (waitAvailableTimeout(timeLeft))
			{
				if (recvfrom(0, 0, &from, &to, &id, &flags)) // Discards the message
				{
					// Now have a message: is it our ACK?
					if (   from == address 
						&& to == _thisAddress 
						&& (flags & RH_FLAGS_ACK)
						&& (id == sequenceNumber))
					{	// Its the ACK we are waiting for
						return true;
					}
					else if ( !(flags & RH_FLAGS_ACK)
							&& (id == _seenIds[from]))
					{	// This is a request we have already received. ACK it again
						acknowledge(id, from);
					}
					// Else discard it
				}
			}
			// Not the one we are waiting for, maybe keep waiting until timeout exhausted
			YIELD;
		}
		// Timeout exhausted, maybe retry
		YIELD;
    }
    // Retries exhausted
    return false;
}

////////////////////////////////////////////////////////////////////
bool RHReliableDatagram::recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags)
{  
    uint8_t _from;
    uint8_t _to;
    uint8_t _id;
    uint8_t _flags;

    // Get the message before its clobbered by the ACK (shared rx and tx buffer in some drivers
    if (available() && recvfrom(buf, len, &_from, &_to, &_id, &_flags))
    {
		// Never ACK an ACK
		if (!(_flags & RH_FLAGS_ACK))
		{
			// Its a normal message for this node, not an ACK
			if (_to != RH_BROADCAST_ADDRESS)
			{
				// Its not a broadcast, so ACK it
				// Acknowledge message with ACK set in flags and ID set to received ID
				acknowledge(_id, _from);
			}
			// If we have not seen this message before, then we are interested in it
			if (_id != _seenIds[_from])
			{
				if (from)  *from =  _from;
				if (to)    *to =    _to;
				if (id)    *id =    _id;
				if (flags) *flags = _flags;
				_seenIds[_from] = _id;
				return true;
			}
			// Else just re-ack it and wait for a new one
		}
    }
    // No message for us available
    return false;
}

bool RHReliableDatagram::recvfromAckTimeout(uint8_t* buf, uint8_t* len, int32_t timeout, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags)
{
    uint32_t starttime = millis();
    int32_t timeLeft;
    while ((timeLeft = timeout - (millis() - starttime)) > 0)
    {
		if (waitAvailableTimeout(timeLeft))
		{
			if (recvfromAck(buf, len, from, to, id, flags))
				return true;
		}
		YIELD;
    }
    return false;
}

uint32_t RHReliableDatagram::retransmissions()
{
    return _retransmissions;
}

void RHReliableDatagram::resetRetransmissions()
{
    _retransmissions = 0;
}
 
void RHReliableDatagram::acknowledge(uint8_t id, uint8_t from)
{
    setHeaderId(id);
    setHeaderFlags(RH_FLAGS_ACK);
    // We would prefer to send a zero length ACK,
    // but if an RH_RF22 receives a 0 length message with a CRC error, it will never receive
    // a 0 length message again, until its reset, which makes everything hang :-(
    // So we send an ACK of 1 octet
    // REVISIT: should we send the RSSI for the information of the sender?
    uint8_t ack = '!';
    sendto(&ack, sizeof(ack), from);
    waitPacketSent();
}

